<?php
/**
 * Smart Library - Smart Framework
 * http://smart-framework.highet.eu
 *
 * @category   SmartL
 * @package    SmartL_DataBinder
 * @subpackage DataAdapter
 * @copyright  Copyright (c) 2005-2008 Smart Framework team - Stepan Sindelar, Vojtech Vit
 * @license    http://www.opensource.org/licenses/bsd-license.php	New BSD License
 */

/**
 * @see SmartL_DataBinder_DataAdapter_Abstract
 */
require_once 'SmartL/DataBinder/DataAdapter/Abstract.php';

/**
 * DataAdapter for objects.
 *
 * Examples of data binding on this adapter:
 * <code>
 *		in binding rules: 'price'
 *		result: $object->price
 *
 *		in binding rules: array('price','vat')
 *		result: $object->price->vat
 *
 *		in binding rules: array( array('name'=>'price','index'=>'normal'), 'vat')
 *		result: $object->price['normal']->vat
 * 
 * 		the definition of indexed array as in the previous example, 
 * 		must be wrapped in other array even if it is olny one binding rule.
 * 		For example:
 * 			right rules: arrray( array('name'=>'name_of_an_array', 'index'=>'index_in_array') )
 * 			wrong: arrray( 'name'=>'name_of_an_array', 'index'=>'index_in_array' )
 * 
 * 		If you want to push something to an array use this rule: array(array('arrayName'))
 * 		This rule has same effect for retrieving value as rule: array('arrayName'), it will return whole array.
 * </code>
 * 
 * This adapter is base class for some other adapters (e.g. Doctrine) and those add 
 * olny logic of retrieving properties by given bindingRule, but meaning of rules should be the same.
 *
 *
 * @package SmartL_DataBinder
 * @subpackage DataAdapter
 */
class SmartL_DataBinder_DataAdapter_Object extends SmartL_DataBinder_DataAdapter_Abstract {
	
	protected function property($name, $value, $object) {
		if (is_array ( $name )) {
			return $this->specialProperty ( $name, $value, $object );
		}
		
		//setting up value
		if ($value !== null) {
			$set_method = 'set' . ucfirst ( $name );
			if (method_exists ( $object, $set_method )) {
				$object->$set_method ( $value );
			} else if (property_exists ( $object, $name ) or isset ( $object->$name )) {
				if (is_array ( $object->$name )) {
					$ref = &$object->$name;
					$ref [] = $value;
				} else {
					$object->$name = $value;
				}
			} else {
				require_once 'SmartL/DataBinder/DataAdpater/Object/Exception.php';
				throw new SmartL_DataBinder_DataAdapter_Object_Exception ( "Property of name {$name} was not found in object {get_class($object)}." );
			}
		}
		//getting and returning value
		$get_method = 'get' . ucfirst ( $name );
		if (method_exists ( $object, $get_method )) {
			return $object->$get_method ();
		} else if (property_exists ( $object, $name )) {
			return $object->$name;
		} else {
			require_once 'SmartL/DataBinder/DataAdpater/Object/Exception.php';
			throw new SmartL_DataBinder_DataAdapter_Object_Exception ( "Property of name {$name} was not found in object {get_class($object)}." );
		}
	}
	
	public function specialProperty($name, $value, $object) {
		if (isset ( $name ['name'] ) and isset ( $name ['index'] )) {
			$prop_name = $name ['name'];
			$ref = &$object->$prop_name;
			if ($value !== null) {
				$ref [$name ['index']] = $value;
			}
			
			return $ref [$name ['index']];
		} elseif (count ( $name ) == 1) {
			reset ( $name );
			$prop_name = current ( $name );
			if ($value !== null) {
				$ref = &$object->$prop_name;
				$ref [] = $value;
			}
			return $object->$prop_name;
		} else {
			require_once 'SmartL/DataBinder/DataAdpater/Object/Exception.php';
			throw new SmartL_DataBinder_DataAdapter_Object_Exception ( "Binding rule {" . implode ( ';', $name ) . "} not recognized by this adapter." );
		}
	}
	
	/**
	 * @param string|array $name
	 * @param mixed $value
	 * @param object $object
	 * @return mixed
	 * @todo Comments, refactor>separate into more methods. Add right exceptions.
	 */
	private function walk($name, $value = null, &$object = null) {
		if ($object === null) {
			//if the object param is null, than this is first call 
			$object = $this->getObject ();
		}
		
		if (is_string ( $name )) {
			//easy, $name is string, so return or set up the property with this name
			return $this->property ( $name, $value, $object );
		} //if $name is not string, it may be an array
		else if (is_array ( $name )) {
			//get first element from this array
			reset ( $name );
			$current = current ( $name );
			unset ( $name [key ( $name )] );
			
			//was it also its last element?
			if (count ( $name ) == 0) {
				//if so, we are in the end
				return $this->property ( $current, $value, $object );
			} else {
				//if not, walk trought
				return $this->walk ( $name, $value, $this->property ( $current, null, $object ) );
			}
		} else {
			require_once 'SmartL/DataBinder/DataAdpater/Object/Exception.php';
			throw new SmartL_DataBinder_DataAdapter_Object_Exception ( "Binding rule {$name} not recognized by this adapter." );
		}
	}
	
	/**
	 * @param string|array $name
	 */
	public function set($name, $value) {
		$this->walk ( $name, $value );
	}
	
	/**
	 * @param string|array $name
	 * @return mixed
	 */
	public function get($name) {
		return $this->walk ( $name );
	}
}